/**
 *    Copyright 2009-2019 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.mapping;

import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.builder.BuilderException;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.reflection.ParamNameUtil;
import org.apache.ibatis.session.Configuration;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.util.*;

/**
 * ResultMap对象定义,其维护了每一个resultMap标签中的详细信息
 * 包括:
 *
 *          id映射、
 *          构造器映射、
 *          属性映射以及完整的映射列表、
 *          是否有嵌套的resultMap、
 *          是否有鉴别器、
 *          是否有嵌套查询
 *
 * @author Clinton Begin
 */
public class ResultMap {
    /**
     * Mybatis配置
     */
    private Configuration configuration;
    /**
     * resultMap的唯一标志
     */
    private String id;
    /**
     * resultMap的返回类型
     */
    private Class<?> type;
    /**
     * resultMap下的所有节点
     */
    private List<ResultMapping> resultMappings;
    /**
     * resultMap下的所有id节点
     */
    private List<ResultMapping> idResultMappings;
    /**
     * resultMap下的所有构造器节点
     */
    private List<ResultMapping> constructorResultMappings;
    /**
     * resultMap下的所有普通属性节点
     */
    private List<ResultMapping> propertyResultMappings;
    /**
     * 映射的列名集合
     */
    private Set<String> mappedColumns;
    /**
     * 映射的所有javaBean属性名，包括ID，构造器，普通。
     */
    private Set<String> mappedProperties;
    /**
     * 鉴别器
     */
    private Discriminator discriminator;
    /**
     * 是否持有嵌套的resultMap,比如association或者collection,
     * 如果它包含discriminator,那么discriminator所持有的ResultMap对象的hasNestedResultMaps属性会影响该属性.
     */
    private boolean hasNestedResultMaps;
    /**
     * 是否有嵌套的查询，比如select属性
     */
    private boolean hasNestedQueries;
    /**
     * 自动映射，该属性会覆盖全局属性
     */
    private Boolean autoMapping;

    private ResultMap() {
    }

    public static class Builder {
        private static final Log log = LogFactory.getLog(Builder.class);

        private ResultMap resultMap = new ResultMap();

        public Builder(Configuration configuration, String id, Class<?> type, List<ResultMapping> resultMappings) {
            this(configuration, id, type, resultMappings, null);
        }

        public Builder(Configuration configuration, String id, Class<?> type, List<ResultMapping> resultMappings, Boolean autoMapping) {
            resultMap.configuration = configuration;
            resultMap.id = id;
            resultMap.type = type;
            resultMap.resultMappings = resultMappings;
            resultMap.autoMapping = autoMapping;
        }

        public Builder discriminator(Discriminator discriminator) {
            resultMap.discriminator = discriminator;
            return this;
        }

        public Class<?> type() {
            return resultMap.type;
        }

        public ResultMap build() {
            if (resultMap.id == null) {
                throw new IllegalArgumentException("ResultMaps must have an id");
            }
            resultMap.mappedColumns = new HashSet<>();
            resultMap.mappedProperties = new HashSet<>();
            resultMap.idResultMappings = new ArrayList<>();
            resultMap.constructorResultMappings = new ArrayList<>();
            resultMap.propertyResultMappings = new ArrayList<>();
            final List<String> constructorArgNames = new ArrayList<>();

            for (ResultMapping resultMapping : resultMap.resultMappings) {
                // 初始化是否含有嵌套查询语句
                resultMap.hasNestedQueries = resultMap.hasNestedQueries || resultMapping.getNestedQueryId() != null;
                // 初始化是否含有嵌套resultMap
                resultMap.hasNestedResultMaps = resultMap.hasNestedResultMaps || (resultMapping.getNestedResultMapId() != null && resultMapping.getResultSet() == null);
                // 获取当前列，包括复合列，
                // 复合列是在org.apache.ibatis.builder.MapperBuilderAssistant.parseCompositeColumnName(String)中解析的。
                // 所有的数据库列都被按顺序添加到resultMap.mappedColumns中
                final String column = resultMapping.getColumn();

                if (column != null) {
                    // 添加映射的列名称
                    resultMap.mappedColumns.add(column.toUpperCase(Locale.ENGLISH));
                } else if (resultMapping.isCompositeResult()) {
                    // 当前是符合列
                    for (ResultMapping compositeResultMapping : resultMapping.getComposites()) {
                        // 获取复合列的列名称
                        final String compositeColumn = compositeResultMapping.getColumn();
                        if (compositeColumn != null) {
                            // 添加映射的列名称
                            resultMap.mappedColumns.add(compositeColumn.toUpperCase(Locale.ENGLISH));
                        }
                    }
                }
                // 获取javaBean的字段类型
                final String property = resultMapping.getProperty();
                if (property != null) {
                    // 添加到映射的属性集合中
                    resultMap.mappedProperties.add(property);
                }
                // 如果本元素具有CONSTRUCTOR标记,则添加到构造函数参数列表,否则添加到普通属性映射列表resultMap.propertyResultMappings
                if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
                    // 处理构造函数，注册到当前的构造函数映射集合中
                    resultMap.constructorResultMappings.add(resultMapping);
                    if (resultMapping.getProperty() != null) {
                        // 添加到构造函数集合内
                        constructorArgNames.add(resultMapping.getProperty());
                    }
                } else {
                    // 不是构造函数,直接添加到普通属性集合内
                    resultMap.propertyResultMappings.add(resultMapping);
                }

                // 如果当前元素有ID标记，则添加到ID映射列表内
                if (resultMapping.getFlags().contains(ResultFlag.ID)) {
                    // 如果是ID标志，添加到ID映射集合中
                    resultMap.idResultMappings.add(resultMapping);
                }
            }

            // 循环结束

            // 如果当前resultMap没有声明ID属性，就把所有的属性都作为ID属性
            if (resultMap.idResultMappings.isEmpty()) {
                resultMap.idResultMappings.addAll(resultMap.resultMappings);
            }

            // 据声明的构造器参数名和类型,反射声明的类,
            // 检查其中是否包含对应参数名和类型的构造器,
            // 如果不存在匹配的构造器,就抛出运行时异常,这是为了确保运行时不会出现异常
            if (!constructorArgNames.isEmpty()) {
                // 获取构造参数中的名称集合
                final List<String> actualArgNames = argNamesOfMatchingConstructor(constructorArgNames);
                if (actualArgNames == null) {
                    throw new BuilderException("Error in result map '" + resultMap.id
                            + "'. Failed to find a constructor in '"
                            + resultMap.getType().getName() + "' by arg names " + constructorArgNames
                            + ". There might be more info in debug log.");
                }
                // 给resultMap的构造器参数排序
                Collections.sort(resultMap.constructorResultMappings, (o1, o2) -> {
                    int paramIdx1 = actualArgNames.indexOf(o1.getProperty());
                    int paramIdx2 = actualArgNames.indexOf(o2.getProperty());
                    return paramIdx1 - paramIdx2;
                });
            }
            // lock down collections
            // 为了避免resultMap的内部结构发生变更, 克隆一个不可修改的集合提供给用户
            resultMap.resultMappings = Collections.unmodifiableList(resultMap.resultMappings);
            resultMap.idResultMappings = Collections.unmodifiableList(resultMap.idResultMappings);
            resultMap.constructorResultMappings = Collections.unmodifiableList(resultMap.constructorResultMappings);
            resultMap.propertyResultMappings = Collections.unmodifiableList(resultMap.propertyResultMappings);
            resultMap.mappedColumns = Collections.unmodifiableSet(resultMap.mappedColumns);
            return resultMap;
        }

        /**
         * @param constructorArgNames 构造函数的入参名称的集合
         * @return
         */
        private List<String> argNamesOfMatchingConstructor(List<String> constructorArgNames) {
            // 获取resultMap对应的javabean的构造函数集合
            Constructor<?>[] constructors = resultMap.type.getDeclaredConstructors();

            for (Constructor<?> constructor : constructors) {
                // 获取当前构造函数的入参列表
                Class<?>[] paramTypes = constructor.getParameterTypes();
                // 处理参数列表和当前入参数量一致的构造函数
                if (constructorArgNames.size() == paramTypes.length) {
                    // 获取构造参数的入参名称集合(有序)
                    List<String> paramNames = getArgNames(constructor);

                    if (constructorArgNames.containsAll(paramNames)  /*参数名称一致*/
                            && argTypesMatch(
                            constructorArgNames
                            , paramTypes /*真正的参数类型集合*/
                            , paramNames/*真正的参数名称集合*/
                    )/*类型是否一致*/
                    ) {
                        return paramNames;
                    }
                }
            }
            return null;
        }

        /**
         * 匹配构造参数的类型
         *
         * @param constructorArgNames 构造函数的参数名称集合
         * @param paramTypes          参数类型
         * @param paramNames          参数名称
         * @return 是否匹配
         */
        private boolean argTypesMatch(final List<String> constructorArgNames,
                                      Class<?>[] paramTypes, List<String> paramNames) {
            for (int i = 0; i < constructorArgNames.size(); i++) {
                // 处理每一个构造参数

                // 获取参数类型
                Class<?> actualType = paramTypes[paramNames.indexOf(constructorArgNames.get(i))];
                // 获取构造函数的参数类型
                Class<?> specifiedType = resultMap.constructorResultMappings.get(i).getJavaType();

                if (!actualType.equals(specifiedType)) {
                    // 判断二者是否一致
                    if (log.isDebugEnabled()) {
                        log.debug("While building result map '" + resultMap.id
                                + "', found a constructor with arg names " + constructorArgNames
                                + ", but the type of '" + constructorArgNames.get(i)
                                + "' did not match. Specified: [" + specifiedType.getName() + "] Declared: ["
                                + actualType.getName() + "]");
                    }
                    return false;
                }
            }
            return true;
        }

        /**
         * 获取构造函数的入参名称集合(按照构造函数的入参顺序存储)
         *
         * @param constructor 构造函数
         * @return 入参名称集合
         */
        private List<String> getArgNames(Constructor<?> constructor) {
            List<String> paramNames = new ArrayList<>();
            List<String> actualParamNames = null;
            // 获取参数列表中的注解集合,每一个构造参数都对应一个Annotation数组。
            final Annotation[][] paramAnnotations = constructor.getParameterAnnotations();

            /*
             * 构造参数的数量
             */
            int paramCount = paramAnnotations.length;

            for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
                // 处理每一个构造参数
                String name = null;
                for (Annotation annotation : paramAnnotations[paramIndex]) {
                    // 寻找当前构造参数上的Param注解
                    if (annotation instanceof Param) {
                        name = ((Param) annotation).value();
                        break;
                    }
                }
                if (name == null && resultMap.configuration.isUseActualParamName()) {
                    // 如果没有添加Param注解，同时还开启了使用真实参数的功能的话，则使用真实参数名称
                    if (actualParamNames == null) {
                        //获取构造参数的所有入参的参数名称集合
                        actualParamNames = ParamNameUtil.getParamNames(constructor);
                    }
                    if (actualParamNames.size() > paramIndex) {

                        name = actualParamNames.get(paramIndex);
                    }
                }
                // 添加参数名称，如果没有找到名称的话，则使用arg+参数索引
                paramNames.add(name != null ? name : "arg" + paramIndex);
            }
            return paramNames;
        }
    }

    public String getId() {
        return id;
    }

    public boolean hasNestedResultMaps() {
        return hasNestedResultMaps;
    }

    public boolean hasNestedQueries() {
        return hasNestedQueries;
    }

    public Class<?> getType() {
        return type;
    }

    public List<ResultMapping> getResultMappings() {
        return resultMappings;
    }

    public List<ResultMapping> getConstructorResultMappings() {
        return constructorResultMappings;
    }

    public List<ResultMapping> getPropertyResultMappings() {
        return propertyResultMappings;
    }

    public List<ResultMapping> getIdResultMappings() {
        return idResultMappings;
    }

    public Set<String> getMappedColumns() {
        return mappedColumns;
    }

    public Set<String> getMappedProperties() {
        return mappedProperties;
    }

    public Discriminator getDiscriminator() {
        return discriminator;
    }

    public void forceNestedResultMaps() {
        hasNestedResultMaps = true;
    }

    public Boolean getAutoMapping() {
        return autoMapping;
    }

}
